use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
use my_macros::add_user_basic_identity;
use serde::{Deserialize, Serialize};

use crate::shared::{self, date_helper};

use super::id_field::IdField;

// #[derive(Debug, Default, Serialize, Deserialize)]
// pub struct UserIdentity {
//     pub id: String,
//     pub username: String,
//     #[serde(rename(serialize = "accountGroupId", deserialize = "accountGroupId"))]
//     pub account_group_id: String,
// }

// /// https://tools.ietf.org/html/rfc7519#section-4.1
// #[derive(Debug, Default, Serialize, Deserialize)]
// pub struct StandardClaims {
//     pub aud: String, // audience
//     pub exp: u64,    // expires_at
//     pub jti: String, // jwt_id
//     pub iat: u64,    // issued_at
//     pub iss: String, // issuer
//     pub nbf: u64,    // not_before
//     pub sub: String, // subject
// }

// #[derive(Debug, Default, Serialize, Deserialize)]
// pub struct BaseClaims {
//     pub standard_claims: StandardClaims,
//     pub user_identity: UserIdentity,
// }

/// DO NOT rename these claims default fields' names. Otherwise it causes miss property error during validating.
///
/// If aud is enabled then must make sure the same value is set during validating. Otherwise Error(InvalidAudience) occurs.
/// validation.set_audience(&[""]);
#[add_user_basic_identity]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct MyClaims {
    // pub id: String,
    // pub username: String,
    #[serde(rename = "accountGroupId")]
    pub account_group_id: IdField,

    pub roles: Vec<String>,
    // pub aud: String, // audience
    pub exp: u64,    // expires_at
    pub jti: String, // jwt_id
    pub iat: u64,    // issued_at
    pub iss: String, // issuer
    pub nbf: u64,    // not_before
    pub sub: String, // subject
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Role {
    pub id: IdField,
    pub authority: String,
    pub title: String,
}

#[add_user_basic_identity]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct MyUserDetails {
    // pub id: String,
    // pub username: String,
    #[serde(rename = "accountGroupId")]
    pub account_group_id: IdField,

    #[serde(rename = "fullName")]
    pub full_name: String,

    #[serde(rename = "avatarUrl")]
    pub avatar_url: String,

    #[serde(rename = "isAccountGroupCreator")]
    pub is_account_group_creator: bool,

    pub roles: Vec<String>,

    #[serde(rename = "fullRoles")]
    pub full_roles: Vec<Role>,

    pub bearer: String,

    #[serde(rename = "accessToken")]
    pub access_token: String,

    #[serde(rename = "generatedAt")]
    pub generated_at: String,

    #[serde(rename = "expiredAt")]
    pub expired_at: String,

    #[serde(rename = "refreshToken")]
    pub refresh_token: String,

    #[serde(rename = "expiresIn")]
    pub expires_in: u64,
}

pub fn generate_token(
    claims: &mut MyClaims,
    signing_key: &str,
    expires_at: &u64,
) -> Result<String, jsonwebtoken::errors::Error> {
    // Sets when token issued.
    claims.iat = shared::date_helper::get_timestamp() as u64;

    // Sets the expiresAt.
    claims.exp = &claims.iat + expires_at;

    // Creates token with provided claims.
    jsonwebtoken::encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(signing_key.as_ref()),
    )
}

/// https://www.sheshbabu.com/posts/rust-error-handling/
/// Return value is claims and if it is valid.
pub fn validate_token(jws: &str, signing_key: &str) -> (Option<MyClaims>, bool) {
    // If aud is enabled then must make sure the same value is set during validating. Otherwise Error(InvalidAudience) occurs.
    // let mut validation = Validation::default();
    // validation.set_audience(&[""]);

    let token_data = jsonwebtoken::decode::<MyClaims>(
        jws,
        &DecodingKey::from_secret(signing_key.as_ref()),
        &Validation::default(),
    );

    match token_data {
        Ok(token) => {
            // When token exipres the Error(ExpiredSignature) should be raised.
            // But sometimes the token is expired but the error is not raised timely. That's why we have to check on it by ourself.
            if token.claims.exp < date_helper::get_timestamp() as u64 {
                return (Some(token.claims), false);
            }

            return (Some(token.claims), true);
        }
        Err(err) => {
            println!("Error: {:?}", err);
            return (None, false);
        }
    };
}
